#!/usr/bin/env python3

# Copyright (c) 2020-2025 Fpemud <fpemud@sina.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.


import os
import abc
from .._handy import TargetType, ServiceType
from .._errors import InvalidMainTargetError, InvalidAuxillaryTargetError
from ._target_main import MainTarget
from ._target_aux import AuxillaryTarget
from ._service_daemon import DaemonService
from ._service_automount import AutoMountService
from ._service_mount import MountService
from ._service_swap import SwapService


class CfgDir(abc.ABC):

    def __init__(self, prefix, supported_target_types, supported_service_types):
        # user should guarantee existence when calling other methods
        # but checker is compatible with non-existence senario

        self._prefix = prefix
        self._targetTypes = supported_target_types
        self._serviceTypes = supported_service_types
        self._unitDirList = None
        self._editableUnitDirList = None

    def has_target(self, target_type, **kwargs):
        try:
            self._getTargetClazzByType(target_type)(self, **kwargs)
            return True
        except FileNotFoundError:
            return False

    def get_target(self, target_type, **kwargs):
        return self._getTargetClazzByType(target_type)(self, **kwargs)

    def try_get_target(self, target_type, **kwargs):
        try:
            return self._getTargetClazzByType(target_type)(self, **kwargs)
        except FileNotFoundError:
            return None

    def query_targets(self, target_type, filter=None):
        ret = []
        nameSet = set()
        for dirPath in self._unitDirList:
            for f in os.listdir(dirPath):
                if f in nameSet:
                    # unit is overlapped
                    continue
                if filter is not None and not filter(f, os.path.join(dirPath, f)):
                    continue
                try:
                    ret.append(self._getTargetClazzByType(target_type))(self, unit_name=f)
                except (InvalidMainTargetError, InvalidAuxillaryTargetError):
                    continue
                nameSet.add(f)
        return ret

    def create_target(self, target_type, dir_path, exist_ok=False, **kwargs):
        assert dir_path in self._editableUnitDirList

        clazz = self._getTargetClazzByType(target_type)

        obj = clazz._exists(self, dir_path, **kwargs)
        if obj is not None and obj.unit_file_path.startswith(dir_path + "/"):
            if exist_ok:
                return obj
            raise FileExistsError("Target already exists: '%s'" % (obj.unit_name))

        return clazz._create(self, dir_path, **kwargs)

    def has_service(self, service_type, **kwargs):
        try:
            self._getServiceClazzByType(service_type)(self, **kwargs)
            return True
        except FileNotFoundError:
            return False

    def get_service(self, service_type, **kwargs):
        self._getServiceClazzByType(service_type)(self, **kwargs)

    def try_get_service(self, service_type, **kwargs):
        try:
            return self._getServiceClazzByType(service_type)(self, **kwargs)
        except FileNotFoundError:
            return None

    def query_services(self, filter=None):
        ret = []
        nameSet = set()
        for dirPath in self._unitDirList:
            for f in os.listdir(dirPath):
                if f in nameSet:
                    # unit is overlapped
                    continue
                if filter is not None and not filter(f, os.path.join(dirPath, f)):
                    continue
                clazz = self._getServiceClazzByUnitName(f)
                if clazz is None:
                    continue
                ret.append(clazz(self, unit_name=f))
                nameSet.add(f)
        return ret

    def create_service(self, service_type, dir_path, exist_ok=False, **kwargs):
        assert dir_path in self._editableUnitDirList

        clazz = self._getServiceClazzByType(service_type)

        obj = clazz._exists(self, dir_path, **kwargs)
        if obj is not None and obj.unit_file_path.startswith(dir_path + "/"):
            if exist_ok:
                return obj
            raise FileExistsError("Service already exists: '%s'" % (obj.unit_name))

        return clazz._create(self, dir_path, **kwargs)

    @abc.abstractmethod
    def check(self, auto_fix, error_callback):
        pass

    def _setUnitDirList(self, unitDirList, editableUnitDirList):
        assert self._unitDirList is None and self._editableUnitDirList is None
        assert unitDirList is not None and editableUnitDirList is not None
        self._unitDirList = unitDirList
        self._editableUnitDirList = editableUnitDirList

    def _findUnitFile(self, unitName):
        for i, p in enumerate(self._unitDirList):
            fullf = os.path.join(p, unitName)
            if os.path.exists(fullf):
                return (fullf, i)
        return (None, None)

    @staticmethod
    def _getTargetClazzByType(target_type):
        if target_type == TargetType.MAIN:
            return MainTarget
        elif target_type == TargetType.AUXILLARY:
            return AuxillaryTarget
        else:
            assert False

    @staticmethod
    def _getServiceClazzByType(service_type):
        if service_type == ServiceType.DAEMON:
            return DaemonService
        elif service_type == ServiceType.AUTOMOUNT:
            return AutoMountService
        elif service_type == ServiceType.MOUNT:
            return MountService
        elif service_type == ServiceType.SWAP:
            return SwapService
        else:
            assert False

    @staticmethod
    def _getServiceClazzByUnitName(unitName):
        if unitName.endswith(".service"):
            return DaemonService
        elif unitName.endswith(".automount"):
            return AutoMountService
        elif unitName.endswith(".mount"):
            return MountService
        elif unitName.endswith(".swap"):
            return SwapService
        else:
            assert None
